/*
 * Backup.java
 *
 * Created on April 27, 2007, 8:06 AM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.atomojo.app.admin;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.URLDecoder;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.atomojo.app.Storage;
import org.atomojo.app.db.DB;
import org.atomojo.app.db.Entry;
import org.atomojo.app.db.EntryMedia;
import org.atomojo.app.db.Feed;
import org.infoset.xml.XMLException;
import org.infoset.xml.util.WriterItemDestination;
import org.restlet.data.Reference;
import org.restlet.representation.Representation;

/**
 *
 * @author alex
 */
public class Backup
{

   static final String FEED_DOCUMENT_NAME = ".feed.atom";
   
   abstract class BackupContext {
      byte [] buffer = new byte[1024*32];
      abstract void push(Feed feed)
         throws IOException;
      abstract void pop()
         throws IOException;
      abstract OutputStream startFeedOutput()
         throws IOException;
      abstract void endFeedOutput()
         throws IOException;
      abstract OutputStream startMediaOutput(EntryMedia media)
         throws IOException;
      abstract void endMediaOutput()
         throws IOException;
   }
   Storage storage;
   DB db;
   Reference resourceBase;
   /** Creates a new instance of Backup */
   public Backup(DB db,Storage storage,Reference resourceBase)
   {
      this.db = db;
      this.storage = storage;
      this.resourceBase = resourceBase;
   }
   
   public void toDirectory(File dir)
      throws SQLException,IOException
   {
      Writer w = new OutputStreamWriter(new FileOutputStream(new File(dir,"_introspection_.xml")),"UTF-8");
      try {
         db.getIntrospection(
            storage,
            "",
            new DB.CollectionLocator() {
               public String makeHref(Feed feed) 
                  throws SQLException
               {
                  String path = feed.getPath();
                  if (path.length()==0) {
                     return ".feed.atom";
                  } else {
                     return path+".feed.atom";
                  }
               }
            },
            new WriterItemDestination(w,"UTF-8")
         );
      } catch (XMLException ex) {
         throw new IOException(ex.getMessage());
      }
      w.flush();
      w.close();
      
      final List<File> dirs = new ArrayList<File>();
      dirs.add(dir);
      BackupContext context = new BackupContext() {
         OutputStream current = null;
         public void push(Feed feed) 
            throws IOException
         {
            String dirName = null;
            try {
               dirName = URLDecoder.decode(feed.getName(),"UTF-8");
            } catch (UnsupportedEncodingException ex) {
               throw new IOException("Cannot decode feed name '"+feed.getName()+"' : "+ex.getMessage());
            }
            File dir = new File(dirs.get(dirs.size()-1),dirName);
            if (!dir.exists() && !dir.mkdir()) {
               throw new IOException("Cannot create directory "+dir.getAbsolutePath());
            }
            dirs.add(dir);
         }
         public void pop() {
            dirs.remove(dirs.size()-1);
         }
         public OutputStream startFeedOutput() 
            throws IOException
         {
            File feedFile = new File(dirs.get(dirs.size()-1),FEED_DOCUMENT_NAME);
            FileOutputStream out = new FileOutputStream(feedFile);
            current = out;
            return out;
         }
         public void endFeedOutput() 
            throws IOException
         {
            if (current!=null) {
               current.close();
            }
         }
         public OutputStream startMediaOutput(EntryMedia media)
            throws IOException
         {
            String name = URLDecoder.decode(media.getName(),"UTF-8");
            File mediaFile = new File(dirs.get(dirs.size()-1),name);
            FileOutputStream mout = new FileOutputStream(mediaFile);
            current = mout;
            return mout;
         }
         public void endMediaOutput()
            throws IOException
         {
            if (current!=null) {
               current.close();
            }
         }
      };
      
      Iterator<Feed> roots = db.getRootFeeds();
      while (roots.hasNext()) {
         Feed root = roots.next();
         backup(context,root);
      }
   }
   
   public void toZip(final String baseName,final File zipFile)
      throws SQLException,IOException
   {
      final ZipOutputStream zipStream = new ZipOutputStream(new FileOutputStream(zipFile));
      zipStream.setMethod(ZipOutputStream.DEFLATED);
      Writer w = new OutputStreamWriter(zipStream,"UTF-8");
      try {
         ZipEntry theEntry = new ZipEntry(baseName+"/_introspection_.xml");
         zipStream.putNextEntry(theEntry);
 
         db.getIntrospection(
            storage,
            "",
            new DB.CollectionLocator() {
               public String makeHref(Feed feed) 
                  throws SQLException
               {
                  String path = feed.getPath();
                  if (path.length()==0) {
                     return ".feed.atom";
                  } else {
                     return path+".feed.atom";
                  }
               }
            },
            new WriterItemDestination(w,"UTF-8")
         );
         zipStream.flush();
         zipStream.closeEntry();
      } catch (XMLException ex) {
         throw new IOException(ex.getMessage());
      }
      
      final List<String> paths = new ArrayList<String>();
      paths.add(baseName+"/");
      BackupContext context = new BackupContext() {
         public void push(Feed feed) 
            throws IOException
         {
            String dirName = null;
            try {
               dirName = URLDecoder.decode(feed.getName(),"UTF-8");
            } catch (UnsupportedEncodingException ex) {
               throw new IOException("Cannot decode feed name '"+feed.getName()+"' : "+ex.getMessage());
            }
            String current = paths.get(paths.size()-1);
            String path = dirName.length()==0 ? current : current+dirName+"/";
            paths.add(path);
         }
         public void pop() {
            paths.remove(paths.size()-1);
         }
         public OutputStream startFeedOutput() 
            throws IOException
         {
            String path = paths.get(paths.size()-1)+FEED_DOCUMENT_NAME;
            db.getLogger().info("Adding: "+path);
            ZipEntry theEntry = new ZipEntry(path);
            zipStream.putNextEntry(theEntry);
            return zipStream;
         }
         public void endFeedOutput() 
            throws IOException
         {
            zipStream.flush();
            zipStream.closeEntry();
         }
         public OutputStream startMediaOutput(EntryMedia media)
            throws IOException
         {
            String name = URLDecoder.decode(media.getName(),"UTF-8");
            String path = paths.get(paths.size()-1)+name;
            db.getLogger().info("Adding: "+path);
            ZipEntry theEntry = new ZipEntry(path);
            zipStream.putNextEntry(theEntry);
            return zipStream;
         }
         public void endMediaOutput()
            throws IOException
         {
            zipStream.flush();
            zipStream.closeEntry();
         }
      };
      Iterator<Feed> roots = db.getRootFeeds();
      while (roots.hasNext()) {
         Feed root = roots.next();
         backup(context,root);
      }
      zipStream.close();
   }
   
   protected void backup(BackupContext context,Feed feed)
      throws SQLException,IOException
   {
      context.push(feed);
      String fpath = feed.getPath();
      Representation feedRep = storage.getFeed(fpath,feed.getUUID(),feed.getEntries());
      if (feedRep!=null) {
         OutputStream out = context.startFeedOutput();
         feedRep.write(out);
         context.endFeedOutput();
         feedRep.release();
         
         Iterator<Entry> entries = feed.getEntries();
         while (entries.hasNext()) {
            Entry entry = entries.next();
            Iterator<EntryMedia> resources = entry.getResources();
            while (resources.hasNext()) {
               EntryMedia media = resources.next();
               Representation mediaRep = storage.getMedia(fpath,feed.getUUID(),media.getName());
               if (mediaRep==null) {
                  throw new IOException("Cannot get media "+media.getName()+" for feed at path "+fpath);
               }
               OutputStream mout = context.startMediaOutput(media);
               mediaRep.write(mout);
               context.endMediaOutput();
               mediaRep.release();
            }
         }
         
         Iterator<Feed> children = feed.getChildren();
         while (children.hasNext()) {
            Feed child = children.next();
            backup(context,child);
         }
      } else {
         throw new IOException("Cannot get feed representation for path "+fpath);
      }
      context.pop();
   }
   
   protected void copy(BackupContext context,InputStream is,OutputStream os)
      throws IOException
   {
      int len;
      while ((len=is.read(context.buffer))>0) {
         os.write(context.buffer,0,len);
      }
   }
}
